﻿(function () {

    var module = angular.module('radical.directives', []);

    /*
     * itemTemplate Directive
     */
    module.factory('itemTemplateDirectiveConfig', ['$log', '$http', '$templateCache', function ($log, $http, $templateCache) {
        return {
            defaultSettings: {
                templatesFolder: '/radical/directives/itemTemplate/templates/',
                dataTypeProperty: 'dataType',
                defaultLoadingTemplate: '<span class="item-template-loading">Loading...</span>',
                defaultLoadingErrorTemplate: '<span>Cannot find any valid template for: {{templateModel}}</span>'
            },
            defaultTemplatesSelector: function (model, settings) {

                var dataTypePropertyValue = model[settings.dataTypeProperty];
                var templateFileName = dataTypePropertyValue;
                $log.debug('itemTemplate directive templateSelector dataTypePropertyValue:', dataTypePropertyValue);

                if (settings.templatesMap) {
                    $log.debug('itemTemplate directive settings have a templatesMap:', settings.templatesMap);

                    if (settings.templatesMap[dataTypePropertyValue]) {
                        $log.debug('itemTemplate directive templateSelector template found in templatesMap');
                        templateFileName = settings.templatesMap[dataTypePropertyValue];
                    } else if (settings.templatesMap['undefined-template']) {
                        $log.debug('itemTemplate directive settings defines an undefined-template property.');
                        templateFileName = settings.templatesMap['undefined-template'];
                    }
                }

                var templateUrl = settings.templatesFolder + templateFileName + '.html';
                $log.debug('itemTemplate directive templateSelector templateUrl:', templateUrl);

                return templateUrl;
            },
            defaultTemplatesLoader: function (templateUrl) {
                return $http.get(templateUrl, { cache: $templateCache });
            }
        };
    }]);

    var itemTemplateDirective = function ($parse, $compile, $log, $rootScope, config) {

        $log.debug('itemTemplate directive injecting function:', config);

        var templatesSelectorProvider = function ($linkAttributes) {
            var selector = null;

            if ($linkAttributes.templateSelector) {
                var parsed = $parse($linkAttributes.templateSelector);
                selector = parsed;
                $log.debug('itemTemplate directive templateSelector found as attribute: ', $linkAttributes.templateSelector, selector);
            } else {
                $log.debug('itemTemplate directive templateSelector not found, creating default.');

                selector = config.defaultTemplatesSelector;
            }

            return selector;
        };

        var templatesLoaderProvider = function ($linkAttributes) {
            var loader = null;

            if ($linkAttributes.templatesLoader) {
                var parsed = $parse($linkAttributes.templatesLoader);
                loader = parsed;
                $log.debug('itemTemplate directive templatesLoader found as attribute: ', $linkAttributes.templatesLoader, loader);
            } else {
                $log.debug('itemTemplate directive templatesLoader not found, creating default.');

                loader = config.defaultTemplatesLoader;
            }

            return loader;
        };

        var settingsProvider = function ($linkAttributes) {

            var settings = null;
            if ($linkAttributes.itemTemplateSettings) {
                var parsed = $parse($linkAttributes.itemTemplateSettings);
                settings = parsed();

                $log.debug('itemTemplate directive settings found as attribute: ', $linkAttributes.itemTemplateSettings, settings);

                if (!settings.templatesFolder) {
                    $log.debug('itemTemplate directive settings templatesFolder is missing, setting default.');
                    settings.templatesFolder = config.defaultSettings.templatesFolder;
                } else {
                    $log.debug('itemTemplate directive settings templatesFolder found:', settings.templatesFolder);
                    var firstChar = settings.templatesFolder.substring(0, 1);
                    if (firstChar === '^') {
                        $log.debug('itemTemplate directive settings templatesFolder starts with the rebasing char ^.');
                        var newTemplatesFolder = config.defaultSettings.templatesFolder + settings.templatesFolder.substring(1);
                        settings.templatesFolder = newTemplatesFolder;
                        $log.debug('itemTemplate directive settings rebased templatesFolder:', settings.templatesFolder);
                    }
                }

                if (!settings.dataTypeProperty) {
                    $log.debug('itemTemplate directive settings dataTypeProperty is missing, setting default.');
                    settings.dataTypeProperty = config.defaultSettings.dataTypeProperty;
                }

            } else {
                $log.debug('itemTemplate directive settings not found, creating defaults.');
                settings = angular.copy(config.defaultSettings);
            }

            $log.debug('itemTemplate directive settings:', settings);

            return settings;
        };

        return {
            restrict: 'EA',
            transclude: false,
            replace: false,
            compile: function compiler($linkAttributes) {

                var link = {
                    post: function ($scope, $linkElement, $linkAttributes) {
                        $log.debug('itemTemplate directive post linker function.');
                        $log.debug('[$scope, $linkElement, $linkAttributes]', $scope, $linkElement, $linkAttributes);

                        var settings = settingsProvider($linkAttributes);
                        var selector = templatesSelectorProvider($linkAttributes);
                        var loader = templatesLoaderProvider($linkAttributes);

                        $linkAttributes.$observe('itemModel', function (newValue) {

                            $log.debug('itemTemplate directive itemModel attribute changed [newValue]:', newValue);

                            $scope.$watch(newValue, function (model) {
                                $log.debug('itemTemplate directive itemModel changed [model]:', model);

                                if (model === null || model === undefined) {
                                    $log.debug('itemTemplate directive model is null, template, if any, will be destroyed.');
                                    $linkElement.empty();
                                } else {

                                    $scope.templateModel = model;

                                    var templateUrl = selector(model, settings);

                                    var composer = function (html) {
                                        var temp = $(html);

                                        $linkElement.empty();

                                        if (temp.length > 1) {
                                            var wrapper = $('<div></div>');
                                            temp.appendTo(wrapper);

                                            wrapper.appendTo($linkElement);
                                        } else {
                                            temp.appendTo($linkElement);
                                        }

                                        $rootScope.safeApply($scope, function () {
                                            var firstChild = $linkElement.children(0);
                                            var compiledElement = $compile($linkElement.html())($scope);

                                            firstChild.replaceWith(compiledElement);
                                        });
                                    };

                                    composer(config.defaultSettings.defaultLoadingTemplate);

                                    loader(templateUrl)
                                        .success(function (data, status, headers, config) {
                                            composer(data);
                                        })
                                        .error(function (data, status, headers, config) {
                                            $log.error('template loafding error: ', data, status, headers);
                                            composer(config.defaultSettings.defaultLoadingErrorTemplate);
                                        });
                                }
                            });
                        });
                    }
                };

                return link;
            }
        };
    };
    itemTemplateDirective.$inject = ['$parse', '$compile', '$log', '$rootScope', 'itemTemplateDirectiveConfig'];

    module.directive('itemTemplate', itemTemplateDirective);

    /*
     * typeahead Directive
     */

    module.factory('typeaheadDirectiveConfig', ['$log', function ($log) {
        return {
            templateUrl: '/radical/directives/typeahead/template.html',
            debouncerDelay: 350,
            tagDisplayValue: function (tag) {
                $log.debug('default tagDisplayValue:', tag);
                return tag.name;
            },
            tagClass: function (tag) {
                $log.debug('default tagClass:', tag);
                return 'label label-info';
            },
            showSearchButton: false
        };
    }]);

    var typeaheadDirective = function ($parse, $compile, $log, $rootScope, $timeout, config) {

        $log.debug('typeahead directive injecting function:', config);

        return {
            restrict: 'EA',
            transclude: true,
            replace: true,
            templateUrl: config.templateUrl,
            scope: {
                search: '&',
                itemSelected: '&',
                items: '=',
                term: '=',
                placeholder: '=',
                tags: '=',
                tagDisplayValue: '&',
                tagClass: '&',
                showSearchButton: '='
            },
            controller: ['$scope', function ($scope) {

                $scope.items = [];
                $scope.hide = false;
                $scope.isBusy = false;

                $log.debug('typehead -> controller $scope: ', $scope);

                this.activate = function (item) {
                    $scope.active = item;
                };

                this.activateNextItem = function () {
                    var index = $scope.items.indexOf($scope.active);

                    this.activate($scope.items[(index + 1) % $scope.items.length]);
                };

                this.activatePreviousItem = function () {
                    var index = $scope.items.indexOf($scope.active);

                    this.activate($scope.items[index === 0 ? $scope.items.length - 1 : index - 1]);
                };

                this.removeLastTag = function () {
                    if ($scope.tags.length > 0) {
                        var last = $scope.tags[$scope.tags.length - 1];
                        $scope.removeTag(last);
                    }
                };

                this.isActive = function (item) {
                    return $scope.active === item;
                };

                this.selectActive = function () {
                    this.select($scope.active);
                    $scope.active = null;
                };

                this.select = function (item) {

                    $scope.hide = true;
                    $scope.focused = true;

                    $scope.itemSelected({ item: item });
                };

                this.selectNone = function () {
                    $scope.active = null;
                    $scope.hide = true;
                    $scope.focused = true;
                    $scope.itemSelected({ item: null });
                };

                //this.onSearchCompleted = function () {
                //    $scope.isBusy = false;
                //    $scope.hide = false;
                //};

                $scope.isVisible = function () {
                    return !$scope.hide && ($scope.focused || $scope.mousedOver) && $scope.items.length > 0;
                };

                var debouncer;

                var searchHandler = function () {
                    debouncer = null;

                    var onSearchCompleted = function () {
                        $rootScope.safeApply($scope, function () {
                            $scope.isBusy = false;
                            $scope.hide = false;
                        });
                    };

                    $rootScope.safeApply($scope, function () {
                        $scope.isBusy = true;
                    });

                    $log.debug('debounce completed -> executing search: [term]', $scope.term);
                    var promise = $scope.search({ term: $scope.term });
                    if (promise) {
                        promise.then(function () {
                            onSearchCompleted();
                        });

                    } else {
                        onSearchCompleted();
                    }

                    return promise;
                };

                var explicitSearchHandler = function () {

                    var onExplicitSearchCompleted = function () {
                        $rootScope.safeApply($scope, function () {
                            $scope.focused = true;
                        });
                    };

                    var promise = searchHandler();
                    if (promise) {
                        promise.then(function () {
                            onExplicitSearchCompleted();
                        });
                    } else {
                        onExplicitSearchCompleted();
                    }
                };

                $scope.removeTag = function (tag) {

                    $log.debug('typehead -> removeTag(tag): ', tag);
                    var index = $.inArray(tag, $scope.tags);
                    if (index !== -1) {
                        $scope.tags.splice(index, 1);
                    }
                };

                $scope.query = function () {

                    $log.debug('typehead -> query(): ', $scope.term);

                    if ($scope.term === '' || $scope.term === null || $scope.term === undefined) {
                        $scope.items = [];
                        return;
                    }

                    if (!debouncer) {
                        debouncer = $.debounce(config.debouncerDelay, false, searchHandler);
                        debouncer();
                        $log.debug('typehead -> query() -> debouncer started.');
                    }
                };

                $scope.explicitQuery = function () {

                    $log.debug('typehead -> explicitSearchHandler(): ', $scope.term);

                    explicitSearchHandler();

                    //if (!debouncer) {

                    //    debouncer = $.debounce(config.debouncerDelay, false, explicitSearchHandler);
                    //    debouncer();
                    //    $log.debug('typehead -> explicitSearchHandler() -> debouncer started.');
                    //}
                };
            }],
            compile: function compiler($linkAttributes) {

                $log.debug('typeahead directive compile function.');

                var link = {
                    post: function (scope, element, attrs, controller) {
                        $log.debug('typeahead directive post link function, scope:', scope);

                        if (!attrs.tagClass) {
                            $log.debug('tagClass is not defined, setting default.');
                            scope.tagClass = function (obj) {
                                return config.tagClass(obj.tag);
                            };
                        }
                        $log.debug('tagClass:', scope.tagClass);

                        if (!attrs.tagDisplayValue) {
                            $log.debug('tagDisplayValue is not defined, setting default.');
                            scope.tagDisplayValue = function (obj) {
                                return config.tagDisplayValue(obj.tag);
                            };
                        }
                        $log.debug('tagDisplayValue:', scope.tagDisplayValue);

                        //if (!attrs.showSearchButton) {
                        //    $log.debug('showSearchButton is not defined, setting default.');
                        //    scope.showSearchButton = config.showSearchButton;
                        //}
                        //$log.debug('showSearchButton:', scope.showSearchButton);

                        var $input = element.find(' > input');
                        var $list = element.find('> div');

                        $log.debug('$input:', $input);

                        $input.bind('focus', function () {
                            scope.$apply(function () { scope.focused = true; });
                        });

                        $input.bind('blur', function () {
                            scope.$apply(function () { scope.focused = false; });
                        });

                        $list.bind('mouseover', function () {
                            scope.$apply(function () { scope.mousedOver = true; });
                        });

                        $list.bind('mouseleave', function () {
                            scope.$apply(function () { scope.mousedOver = false; });
                        });

                        $input.bind('keyup', function (e) {
                            $log.debug('input -> keyup:', e.keyCode, e);

                            if (e.keyCode === 9 || e.keyCode === 13) {
                                scope.$apply(function () { controller.selectActive(); });
                            }

                            if (e.keyCode === 27) {
                                scope.$apply(function () {
                                    controller.selectNone();
                                });
                            }
                        });

                        $input.bind('keydown', function (e) {

                            $log.debug('input -> keydown:', e.keyCode, e);

                            if (e.keyCode === 8 && e.target.value === '') {
                                e.preventDefault();
                                scope.$apply(function () {
                                    scope.hide = true;
                                    controller.removeLastTag();
                                });
                            }

                            if (e.keyCode === 9 && scope.isVisible()) {
                                e.preventDefault();
                            }

                            if (e.keyCode === 13 || e.keyCode === 27) {
                                e.preventDefault();
                            }

                            if (e.keyCode === 40) {
                                e.preventDefault();
                                scope.$apply(function () { controller.activateNextItem(); });
                            }

                            if (e.keyCode === 38) {
                                e.preventDefault();
                                scope.$apply(function () { controller.activatePreviousItem(); });
                            }

                            if (e.keyCode === 13 && e.ctrlKey) {
                                e.preventDefault();
                                scope.explicitQuery();
                            }
                        });

                        scope.$watch('items', function (items) {
                            controller.activate(items.length ? items[0] : null);
                            //controller.onSearchCompleted();
                        });

                        scope.$watch('focused', function (focused) {

                            if (focused) {
                                $timeout(function () {
                                    $input.focus();
                                }, 0, false);
                            }
                        });

                        scope.$watch('isVisible()', function (visible) {
                            if (visible) {
                                var pos = $input.position();
                                var height = $input[0].offsetHeight;

                                $list.css({
                                    top: pos.top + height + 4,
                                    left: pos.left - 6,
                                    position: 'absolute',
                                    display: 'block',
                                    'z-index': '9999'
                                });
                            } else {
                                $list.css('display', 'none');
                            }
                        });
                    }
                };

                return link;
            }
        };
    };
    typeaheadDirective.$inject = ['$parse', '$compile', '$log', '$rootScope', '$timeout', 'typeaheadDirectiveConfig'];

    module.directive('typeahead', typeaheadDirective);

    /*
     * typeahead-item Directive
     */

    var typeaheadItemDirective = function ($log) {

        //$log.debug('typeahead-item directive injecting function:');

        return {
            require: '^typeahead',
            compile: function compiler($linkAttributes) {

                //$log.debug('typeahead-item directive compile function.');

                var link = {
                    post: function (scope, element, attrs, controller) {

                        //$log.debug('typeahead-item directive post link function.', scope);

                        var item = scope.$eval(attrs.typeaheadItem);

                        scope.$watch(function () {
                            return controller.isActive(item);
                        }, function (active) {
                            if (active) {
                                element.addClass('active');
                            } else {
                                element.removeClass('active');
                            }
                        });

                        element.bind('mouseenter', function (e) {
                            scope.$apply(function () { controller.activate(item); });
                        });

                        element.bind('click', function (e) {
                            scope.$apply(function () { controller.select(item); });
                        });
                    }
                };

                return link;
            }
        };
    };
    typeaheadItemDirective.$inject = ['$log'];

    module.directive('typeaheadItem', typeaheadItemDirective);

    var INTEGER_REGEXP = /^\-?\d*$/;
    module.directive('integer', function () {
        return {
            require: 'ngModel',
            link: function (scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift(function (viewValue) {
                    if (INTEGER_REGEXP.test(viewValue)) {
                        // it is valid
                        ctrl.$setValidity('integer', true);
                        return viewValue;
                    } else {
                        // it is invalid, return undefined (no model update)
                        ctrl.$setValidity('integer', false);
                        return undefined;
                    }
                });
            }
        };
    });

    var FLOAT_REGEXP = /^\-?\d+((\.|\,)\d+)?$/;
    module.directive('smartFloat', function () {
        return {
            require: 'ngModel',
            link: function (scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift(function (viewValue) {
                    if (FLOAT_REGEXP.test(viewValue)) {
                        ctrl.$setValidity('float', true);
                        return parseFloat(viewValue.replace(',', '.'));
                    } else {
                        ctrl.$setValidity('float', false);
                        return undefined;
                    }
                });
            }
        };
    });

    /*
     * radicalSubmit Directive
     */
    var submitDirective = function ($parse, $q, $timeout, $log) {
        return {
            restrict: 'EA',
            require: ['radicalSubmit', 'form'],
            controller: function ($scope) {
                //var self = this,
                //    parentForm = element.parent().controller('form');

                //if (parentForm) {
                //    parentForm.$addControl(self);
                //}

                this.submitAttempted = false;

                this.setSubmitAttempted = function () {
                    this.submitAttempted = true;
                };
            },
            compile: function compile(tElement, tAttrs, transclude) {
                return {
                    pre: function (scope, iElement, iAttrs, controller) {
                        //$log.debug('PRE');

                        //$log.debug('radicalSubmit scope', scope);
                        //$log.debug('radicalSubmit iElement', iElement);
                        //$log.debug('radicalSubmit iAttrs', iAttrs);
                        //$log.debug('radicalSubmit controller', controller);

                        scope.radical = scope.radical || {};

                        //var parentFormCtrl = iElement.parent();//.controller('form');
                        //$log.debug('radicalSubmit parentFormCtrl', parentFormCtrl);

                        scope.radical[iAttrs.name] = controller[0];

                        //$log.debug('radicalSubmit scope', scope);
                        //$log.debug('radicalSubmit iElement', iElement);
                        //$log.debug('radicalSubmit iAttrs', iAttrs);
                        //$log.debug('radicalSubmit controller', controller);
                    },
                    post: function (scope, iElement, iAttrs, controller) {
                        //$log.debug('POST');

                        //$log.debug('radicalSubmit link function');
                        //$log.debug('radicalSubmit scope', scope);
                        //$log.debug('radicalSubmit iElement', iElement);
                        //$log.debug('radicalSubmit iAttrs', iAttrs);
                        //$log.debug('radicalSubmit controller', controller);

                        var fn = $parse(iAttrs.radicalSubmit);
                        //$log.debug('fn', fn);

                        iElement.bind('submit', function (event) {
                            //$log.debug('radicalSubmit submit');
                            //$log.debug('radicalSubmit scope', scope);
                            //$log.debug('radicalSubmit iElement', iElement);
                            //$log.debug('radicalSubmit iAttrs', iAttrs);
                            //$log.debug('radicalSubmit controller', controller);

                            controller[0].setSubmitAttempted();
                            if (!scope.$$phase) {
                                scope.$apply();
                            }

                            //$log.debug('controller[0].submitAttempted', controller[0].submitAttempted);

                            // if form is not valid cancel it.
                            if (!controller[1].$valid) {
                                //$log.debug('invalid form');

                                return false;
                            }

                            scope.$apply(function () {
                                //$log.debug('valid form');

                                fn(scope, { $event: event });
                            });
                        });
                    }
                }
            }
        };
    };
    submitDirective.$inject = ['$parse', '$q', '$timeout', '$log'];

    module.directive('radicalSubmit', submitDirective);
}());